Skip to content

Add openai_agents streaming sample - #301

Open
jssmith wants to merge 6 commits into
temporalio:mainfrom
jssmith:openai-agents-streaming-sample
Open

Add openai_agents streaming sample#301
jssmith wants to merge 6 commits into
temporalio:mainfrom
jssmith:openai-agents-streaming-sample

Conversation

@jssmith

@jssmith jssmith commented Apr 30, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds an openai_agents/streaming/ sample demonstrating buffered token streaming for OpenAI Agents-backed workflows via temporalio.contrib.workflow_streams (experimental, contrib/pubsub branch of sdk-python).

The OpenAI Agents plugin's ModelActivityParameters carries a streaming_event_topic; the model activity publishes raw TResponseStreamEvents to that topic, batched over streaming_event_batch_interval (default 100ms). The workflow emits a sentinel on a done topic when Runner.run_streamed finishes; subscribers iterate (events, done) and break on the sentinel. race_with_workflow handles the case where the workflow fails before publishing the sentinel.

Two scenarios:

  • stream_text — text-delta events from a simple haiku agent
  • stream_items — agent-update / handoff / tool-call events across a multi-agent workflow with a joke-rating activity

This is the second half of #299. Independent of the workflow_streams basics (separate PR), though both target the same sdk-python contrib branch.

Test plan

  • Build sdk-python from contrib/pubsub and install into the samples uv environment
  • OPENAI_API_KEY=... uv run openai_agents/streaming/run_worker.py
  • uv run openai_agents/streaming/run_stream_text_workflow.py — verify text deltas print as small bursts
  • uv run openai_agents/streaming/run_stream_items_workflow.py — verify agent-update / tool-call / message-output events render in order

Demonstrates buffered token streaming for OpenAI Agents-backed
workflows via temporalio.contrib.workflow_streams (experimental,
contrib/pubsub branch of sdk-python). The OpenAI Agents plugin's
ModelActivityParameters carries a streaming_event_topic; the model
activity publishes raw stream events to that topic with a
configurable flush interval (default 100ms), and the workflow
emits a sentinel on a "done" topic when Runner.run_streamed
finishes. Subscribers iterate (events, done) and break on the
sentinel — race_with_workflow handles the case where the workflow
fails before publishing the sentinel.

Two scenarios:
- stream_text: text-delta events from a simple haiku agent
- stream_items: agent-update / handoff / tool-call events across
  a multi-agent workflow with a joke-rating activity
@jssmith
jssmith requested a review from a team as a code owner April 30, 2026 03:34
@jssmith jssmith mentioned this pull request Apr 30, 2026
@jssmith
jssmith marked this pull request as draft April 30, 2026 03:37
jssmith and others added 5 commits April 29, 2026 20:49
run_stream_items_workflow: print the workflow's final result after
the streamed events render — matches run_stream_text_workflow and
makes streamed-vs-final parity visible.
The sample was written against the contrib/pubsub branch of sdk-python.
Workflow Streams and OpenAI Agents streaming both shipped in 1.30.0, with
some renames and one behavioral difference, so bring the sample in line:

- ModelActivityParameters.streaming_event_topic is now streaming_topic,
  and streaming_event_batch_interval is streaming_batch_interval.
- subscribe() without result_type decodes payloads rather than handing
  back a raw Payload. Pass result_type=RawValue and decode per topic,
  matching the workflow_streams samples.
- subscribe() exits cleanly once the workflow reaches a terminal state,
  so the race_with_workflow helper is unnecessary: break on the
  terminator, then await handle.result(), which raises if the workflow
  failed. Verified against a terminated workflow.
- Workflows hold the run open briefly after publishing the terminator so
  a subscriber's next poll can drain the tail of the stream, which lives
  in workflow memory.

Fix the stream_items scenario. The streaming activity publishes native
OpenAI events, not the agents-SDK StreamEvent wrappers, so the
agent-update / tool-call / message-output events the subscriber was
matching on never appear on that topic. The agents SDK builds those
inside the workflow, so the workflow now publishes them itself as a
serializable ItemEvent on its own topic (the SDK's own event types carry
the originating Agent, which holds tool callables). stream_events()
resolves a turn at a time, so the play-by-play still arrives
progressively. For the same reason, the stream_text subscriber now
matches ResponseTextDeltaEvent directly instead of unwrapping a
raw_response_event.

Also move both workflow module docstrings above the imports, where they
are actually docstrings, and drop the stale contrib/pubsub install notes
from the READMEs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Covers both scenarios against a scripted streaming model, so no
OPENAI_API_KEY is needed: the plugin accepts a model_provider directly,
so unlike the other AI sample tests this one needs no monkeypatching.

- stream_text: the text arrives as several native OpenAI delta events
  that reassemble into exactly what the workflow returns.
- stream_items: the workflow-published events arrive in order —
  agent_updated, tool_call, tool_output, message_output.

Both subscribe the same way the runner scripts do (one iterator over the
event and terminator topics, RawValue payloads decoded per topic) and
assert the terminator is seen, which is what lets the subscriber stop
without racing the workflow's completion.

These are the first tests under tests/openai_agents. The directory should
also be listed in CODEOWNERS alongside the other AI sample test
directories, but this branch predates that block, so adding it here would
conflict with main.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Matches the other AI sample test directories. Deferred until after the
merge from main, which is where that block came from.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@brianstrauch

Copy link
Copy Markdown
Member

Picked this up to get it over the line. Four commits pushed on top of yours (nothing rewritten, main merged in rather than rebased). The PR description above is now stale — summary of what changed:

Updated for the released API

Workflow Streams and OpenAI Agents streaming both shipped in temporalio 1.30.0, so the contrib/pubsub install step is gone. Along with that:

  • streaming_event_topicstreaming_topic, streaming_event_batch_intervalstreaming_batch_interval.
  • subscribe() without result_type now decodes payloads rather than handing back a raw Payload, so the assert isinstance(item.data, Payload) no longer holds. Subscribers pass result_type=RawValue and decode per topic, matching the merged workflow_streams/ samples.
  • Dropped race_with_workflow (all of shared.py's 79 lines). Released subscribe() exits cleanly once the workflow reaches a terminal state, so the "subscriber blocked on its next poll forever" case it guarded against doesn't arise — break on the terminator, then await handle.result(). Confirmed by terminating a running workflow mid-subscribe: iterator exhausted cleanly, handle.result() raised WorkflowFailureError.
  • Workflows now hold the run open briefly after publishing the terminator so a subscriber's last poll can drain the tail, same as workflow_streams/workflows/llm_workflow.py. The log lives in workflow memory, so it's gone the moment the run completes.

stream_items needed a rethink

The streaming activity publishes native OpenAI events, not the agents-SDK StreamEvent wrappers — so the agent_updated_stream_event / run_item_stream_event / tool_call_item events that subscriber matched on never appear on that topic, and it would have rendered nothing but === Run starting ===. Those higher-level events are built by the agents SDK inside the workflow.

So the workflow now publishes them itself, as a small serializable ItemEvent on its own items topic. It can't forward the SDK's own event types — RunItemStreamEvent carries the originating Agent, which holds tool callables. stream_events() resolves a turn at a time (one activity per model call), so the play-by-play still reaches the subscriber progressively rather than in one lump.

Same root cause in stream_text: it unwrapped a raw_response_event that isn't there. It now matches ResponseTextDeltaEvent directly.

Tests

tests/openai_agents/streaming_test.py — first tests under that directory, shaped after tests/strands_plugin/streaming_test.py. A scripted streaming model (_mock_model.py) means no OPENAI_API_KEY is needed; unlike the strands and ADK mocks it needs no monkeypatching, since OpenAIAgentsPlugin accepts a model_provider directly. test_stream_text asserts the deltas arrive unwrapped and reassemble into exactly the workflow's return value (this is the regression test for the wrapped-vs-native bug); test_stream_items asserts the four event kinds arrive in order. Both subscribe the way the runner scripts do and assert the terminator is seen.

Green on the local dev server and under --workflow-environment time-skipping, ~3s per run. poe lint clean.

Smaller things

Both workflow module docstrings sat below the imports, so they were dead string expressions rather than docstrings — moved to the top of the file. READMEs rewritten: branch-install notes dropped, the native-vs-wrapped event distinction documented, structure aligned with openai_agents/basic/README.md.

Still worth doing before merge

I verified end-to-end against a scripted model, not a live one. A run with a real OPENAI_API_KEY would confirm the batched-burst cadence the README describes, which is the one claim a fake model can't substantiate.

@brianstrauch
brianstrauch marked this pull request as ready for review July 30, 2026 17:29
@brianstrauch
brianstrauch requested a review from a team as a code owner July 30, 2026 17:29
@brianstrauch
brianstrauch requested a review from Copilot July 30, 2026 17:29

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new openai_agents/streaming/ sample showing how to expose buffered model token streaming from OpenAI Agents-backed workflows to external subscribers using temporalio.contrib.workflow_streams, plus tests and supporting mock model infrastructure.

Changes:

  • Introduces two streaming workflows (stream_text, stream_items) and runnable scripts to start a worker and subscribe to streamed output.
  • Adds a dedicated README explaining buffered token streaming semantics, topics, and subscriber patterns.
  • Adds automated tests for both streaming scenarios, including a scripted streaming model provider for deterministic test runs.

Reviewed changes

Copilot reviewed 12 out of 16 changed files in this pull request and generated no comments.

Show a summary per file
File Description
tests/openai_agents/streaming_test.py End-to-end tests that subscribe to workflow streams and assert streamed event shapes and final results.
tests/openai_agents/_mock_model.py Scripted streaming ModelProvider used by tests to deterministically emit deltas, tool calls, and completion events.
tests/openai_agents/__init__.py Marks tests.openai_agents as a package for test imports.
openai_agents/streaming/workflows/stream_text_workflow.py Workflow that runs an agent via Runner.run_streamed and publishes a completion sentinel for subscribers.
openai_agents/streaming/workflows/stream_items_workflow.py Workflow that publishes higher-level agent/tool/message events to its own stream topic while the run progresses.
openai_agents/streaming/workflows/__init__.py Marks workflows directory as a package.
openai_agents/streaming/shared.py Shared constants (task queue, topic names, drain interval) and a serializable ItemEvent type.
openai_agents/streaming/run_worker.py Worker entrypoint registering workflows/activities and configuring the plugin’s streaming topic.
openai_agents/streaming/run_stream_text_workflow.py Starter/subscriber script that renders ResponseTextDeltaEvent output as it streams.
openai_agents/streaming/run_stream_items_workflow.py Starter/subscriber script that prints higher-level workflow-published ItemEvents.
openai_agents/streaming/README.md Documentation for buffered streaming behavior, configuration, and how the sample is structured.
openai_agents/streaming/activities/joke_activities.py Small activity used as a tool in the multi-agent streaming example.
openai_agents/streaming/activities/__init__.py Marks activities directory as a package.
openai_agents/streaming/__init__.py Marks streaming sample directory as a package.
openai_agents/README.md Adds the new Streaming sample to the OpenAI Agents samples index.
.github/CODEOWNERS Assigns code ownership for tests/openai_agents/ to the AI SDK owners.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants